using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Runtime.InteropServices;

namespace WolvenKit.RED3.CR2W.SRT
{
    [StructLayout(LayoutKind.Explicit, Size = 12)]
    [TypeConverter(typeof(ExpandableObjectConverter))]
    public struct Vec3
    {
        [FieldOffset(0)]
        private float x1;

        [FieldOffset(4)]
        private float y1;

        [FieldOffset(8)]
        private float z1;

        public float x { get => x1; set => x1 = value; }
        public float y { get => y1; set => y1 = value; }
        public float z { get => z1; set => z1 = value; }

        public override string ToString() => $"[{x},{y},{z}]";
    }

    ///////////////////////////////////////////////////////////////////////
    //  Structure SCollisionObject
    //
    //	There are two collision object types: sphere and capsules; m_vCenter1 and
    //	m_vCenter2 will be equal for spheres.
    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    [TypeConverter(typeof(ExpandableObjectConverter))]
    public struct SCollisionObject
    {
        private string pUserString;
        private Vec3 vCenter1;
        private Vec3 vCenter2;
        private float fRadius;

        public string m_pUserString { get => pUserString; set => pUserString = value; }               // any data entered by the artist in the Modeler app
        public Vec3 m_vCenter1 { get => vCenter1; set => vCenter1 = value; }                     // center of sphere or one end of a capsule
        public Vec3 m_vCenter2 { get => vCenter2; set => vCenter2 = value; }                     // other end of capsule or same as m_vCenter1 if sphere
        public float m_fRadius { get => fRadius; set => fRadius = value; }                   // radius of the sphere or capsule

        public override string ToString() => pUserString;
    }

    ///////////////////////////////////////////////////////////////////////
    //  Structure SVerticalBillboards
    //
    //	All of the data needed to house 360-degree billboard cutouts as
    //	generated by the Compiler app.
    [TypeConverter(typeof(ExpandableObjectConverter))]
    public struct SVerticalBillboards
    {
        private float m_fWidth;                // width of the billboard, governed by tree extents
        private float m_fTopPos;               // top-most point of the billboard, governed by tree height
        private float m_fBottomPos;            // bottom-most point, can be below zero for trees with roots, etc.
        private int m_nNumBillboards;      // number of 360-degree billboards generated by Compiler app

        private float[] m_pTexCoords;         // 4 entries per image (left u, bottom v, width u, height v)
        private byte[] m_pRotated;              // one entry per image, 1 = rotated, 0 = standard

        // the Compiler app can generate non-rectangular cutouts, reducing the fill requirements at the
        // cost of added vertices; these vertices are
        private int m_nNumCutoutVertices;

        private float[] m_pCutoutVertices;        // # elements = 2 * m_nNumCutoutVertices [ (x,y) pairs ];
                                                  // [x,y] values are range [0,1] as percent across height & width

        private int m_nNumCutoutIndices;
        private ushort[] m_pCutoutIndices;      // # elements = m_nNumCutoutIndices, indexed triangles

        #region Properties

        public float FWidth { get => m_fWidth; set => m_fWidth = value; }
        public float FTopPos { get => m_fTopPos; set => m_fTopPos = value; }
        public float FBottomPos { get => m_fBottomPos; set => m_fBottomPos = value; }
        public int NNumBillboards { get => m_nNumBillboards; set => m_nNumBillboards = value; }

        public float[] PTexCoords { get => m_pTexCoords; set => m_pTexCoords = value; }
        public byte[] PRotated { get => m_pRotated; set => m_pRotated = value; }
        public int NNumCutoutVertices { get => m_nNumCutoutVertices; set => m_nNumCutoutVertices = value; }
        public float[] PCutoutVertices { get => m_pCutoutVertices; set => m_pCutoutVertices = value; }
        public int NNumCutoutIndices { get => m_nNumCutoutIndices; set => m_nNumCutoutIndices = value; }
        public ushort[] PCutoutIndices { get => m_pCutoutIndices; set => m_pCutoutIndices = value; }

        #endregion Properties
    }

    ///////////////////////////////////////////////////////////////////////
    //  Structure SHorizontalBillboard
    //
    //	Housing for the much-simpler horizontal billboard (no cutouts or
    //	multiple views used)
    [TypeConverter(typeof(ExpandableObjectConverter))]
    public struct SHorizontalBillboard
    {
        private bool m_bPresent;                 // true if an overhead billboard was exported using Compiler
        private Vec3[] m_avPositions;          // four sets of (xyz) to render the overhead square
        private float[] m_afTexCoords;            // 4 * (s,t) pairs of diffuse/normal texcoords

        public bool BPresent { get => m_bPresent; set => m_bPresent = value; }
        public Vec3[] AvPositions { get => m_avPositions; set => m_avPositions = value; }
        public float[] AfTexCoords { get => m_afTexCoords; set => m_afTexCoords = value; }
    }

    ///////////////////////////////////////////////////////////////////////
    //  Enumeration ERenderPass
    //
    //	Three main render passes are supported by the SDK by default:
    //
    //		- Main (forward rendering lit, or deferred MRT)
    //		- Depth-only prepass
    //		- Shadow cast

    public enum ERenderPass
    {
        RENDER_PASS_MAIN,
        RENDER_PASS_DEPTH_PREPASS,
        RENDER_PASS_SHADOW_CAST,
        RENDER_PASS_COUNT
    }

    [TypeConverter(typeof(ExpandableObjectConverter))]
    public class SGeometry
    {
        public int NNum3dRenderStates { get; set; }
        public bool BDepthOnlyIncluded { get; set; }
        public bool BShadowCastIncluded { get; set; }
        public string StrShaderPath { get; set; }
        public SRenderState[] P3dRenderStateMain { get; set; }
        public SRenderState[] P3dRenderStateDepth { get; set; }
        public SRenderState[] P3dRenderStateShadow { get; set; }
        public SRenderState ABillboardRenderStateMain { get; set; }
        public SRenderState ABillboardRenderStateDepth { get; set; }
        public SRenderState ABillboardRenderStateShadow { get; set; }
        public int NNumLods { get; set; }
        public SLod[] PLods { get; set; }
        public SVerticalBillboards SVertBBs { get; set; }
        public SHorizontalBillboard SHorzBB { get; set; }

        public override string ToString() => $"{StrShaderPath}";
    }

    ///////////////////////////////////////////////////////////////////////
    //  Structure SLod
    //[StructLayout(LayoutKind.Explicit, Size = 24, Pack = 1)]
    //public struct SLod
    //   {
    //       [FieldOffset(0)] int m_nNumDrawCalls;
    //       [FieldOffset(4)] CPaddedPtr<SDrawCall> m_pDrawCalls;   // typecast to SDrawCall*

    //       [FieldOffset(12)] int m_nNumBones;
    //       [FieldOffset(16)] CPaddedPtr<SBone> m_pBones;       // typecast to SBone*

    //       public int NNumDrawCalls { get => m_nNumDrawCalls; set => m_nNumDrawCalls = value; }
    //       public CPaddedPtr<SDrawCall> PDrawCalls { get => m_pDrawCalls; set => m_pDrawCalls = value; }
    //       public int NNumBones { get => m_nNumBones; set => m_nNumBones = value; }
    //       public CPaddedPtr<SBone> PBones { get => m_pBones; set => m_pBones = value; }
    //   }

    [TypeConverter(typeof(ExpandableObjectConverter))]
    public class SLod
    {
        public int NNumDrawCalls { get; set; }
        public SDrawCall[] PDrawCalls { get; set; }
        public int NNumBones { get; set; }
        public SBone[] PBones { get; set; }
    }

    ///////////////////////////////////////////////////////////////////////
    //  Structure SDrawCall

    // structure is serialized in Parser.cpp
    //   [StructLayout(LayoutKind.Explicit, Size = 40, Pack = 1)]
    //public struct SDrawCall
    //{
    //       // render state
    //       [FieldOffset(0)] CPaddedPtr<SRenderState> m_pRenderState;      // typecast to SRenderState*
    //       [FieldOffset(8)] int m_nRenderStateIndex;

    //       // vertices
    //       [FieldOffset(12)] int m_nNumVertices;
    //       [FieldOffset(16)] CPaddedPtr<byte> m_pVertexData;       // mixed types, but can typecast to st_byte*

    //       // indices
    //       [FieldOffset(24)] int m_nNumIndices;
    //       [FieldOffset(28)] bool m_b32BitIndices;
    //       [FieldOffset(32)] CPaddedPtr<byte> m_pIndexData;       // can typecast to st_byte*, then to st_uint32* or st_uint16*,  // depending on m_b32BitIndices

    //	public CPaddedPtr<SRenderState> PRenderState { get => m_pRenderState; set => m_pRenderState = value; }
    //       public int NRenderStateIndex { get => m_nRenderStateIndex; set => m_nRenderStateIndex = value; }
    //       public int NNumVertices { get => m_nNumVertices; set => m_nNumVertices = value; }
    //       public CPaddedPtr<byte> PVertexData { get => m_pVertexData; set => m_pVertexData = value; }
    //       public int NNumIndices { get => m_nNumIndices; set => m_nNumIndices = value; }
    //       public bool B32BitIndices { get => m_b32BitIndices; set => m_b32BitIndices = value; }
    //       public CPaddedPtr<byte> PIndexData { get => m_pIndexData; set => m_pIndexData = value; }

    //   };

    [TypeConverter(typeof(ExpandableObjectConverter))]
    public class SDrawCall
    {
        public SRenderState PRenderState { get; set; }
        public int NRenderStateIndex { get; set; }
        public int NNumVertices { get; set; }
        public byte[] PVertexData { get; set; }
        public int NNumIndices { get; set; }
        public bool B32BitIndices { get; set; }
        public byte[] PIndexData { get; set; }
    };

    ///////////////////////////////////////////////////////////////////////
    //  Structure SBone
    [StructLayout(LayoutKind.Explicit, Size = 48)]
    [TypeConverter(typeof(ExpandableObjectConverter))]
    public struct SBone
    {
        [FieldOffset(0)] private int m_nID;
        [FieldOffset(4)] private int m_nParentID;
        [FieldOffset(8)] private Vec3 m_vStart;
        [FieldOffset(20)] private Vec3 m_vEnd;
        [FieldOffset(32)] private float m_fRadius;
        [FieldOffset(36)] private float m_fMass;
        [FieldOffset(40)] private float m_fMassWithChildren;
        [FieldOffset(44)] private bool m_bBreakable;

        public int NID { get => m_nID; set => m_nID = value; }
        public int NParentID { get => m_nParentID; set => m_nParentID = value; }
        public Vec3 VStart { get => m_vStart; set => m_vStart = value; }
        public Vec3 VEnd { get => m_vEnd; set => m_vEnd = value; }
        public float FRadius { get => m_fRadius; set => m_fRadius = value; }
        public float FMass { get => m_fMass; set => m_fMass = value; }
        public float FMassWithChildren { get => m_fMassWithChildren; set => m_fMassWithChildren = value; }
        public bool BBreakable { get => m_bBreakable; set => m_bBreakable = value; }
    }

    ///////////////////////////////////////////////////////////////////////
    //  Enumeration ETextureLayer
    //
    //	Used to access texture layers in SRenderState but also used as texture
    //	registers in shaders.

    internal enum ETextureLayer
    {
        TL_DIFFUSE,
        TL_NORMAL,
        TL_DETAIL_DIFFUSE,
        TL_DETAIL_NORMAL,
        TL_SPECULAR_MASK,
        TL_TRANSMISSION_MASK,
        TL_AUX_ATLAS1,
        TL_AUX_ATLAS2,

        TL_NUM_TEX_LAYERS
    }

    //[StructLayout(LayoutKind.Explicit, Size = 8)]
    [TypeConverter(typeof(ExpandableObjectConverter))]
    public struct CPaddedPtr
    {
        //[FieldOffset(0)]
        private int idx;

        //[FieldOffset(4)]
        private int pad;

        private int Idx { get => idx; set => idx = value; }
        private int Pad { get => pad; set => pad = value; }
        public string Val { get; private set; }

        public void Read(BinaryReader br, List<string> stringtable)
        {
            idx = br.ReadInt32();
            pad = br.ReadInt32();

            Val = stringtable[idx];
        }

        public void Write(BinaryWriter bw, List<string> stringtable)
        {
            int id = 0;
            for (int i = 0; i < stringtable.Count; i++)
            {
                if (Val == stringtable[i])
                    id = i;
            }
            bw.Write(id);
            bw.Write(pad);
        }

        public override string ToString() => string.IsNullOrEmpty(Val) ? "" : Val;
    }

    ///////////////////////////////////////////////////////////////////////
    //  Structure SRenderState
    //
    //	Every draw call is associated with a SRenderState object which holds
    //	numerous effect states. A lot of these are determined by the Effect LOD
    //	dialog in the Compiler application.

    // Note that any changes to SRenderState's CStringPtr member variables will necessitate an
    // update to CCore::DeleteGeometry() and SwapEndianRenderState in Parser.cpp

    //[StructLayout(LayoutKind.Explicit, Size = 720, Pack = 1)]
    //[StructLayout(LayoutKind.Sequential, Pack = 1)]
    [TypeConverter(typeof(ExpandableObjectConverter))]
    public struct SRenderState
    {
        // specify the # of shadow maps to use in the example cascaded shadow map system
        //      enum EShadowConfig
        //{
        //	SHADOW_CONFIG_OFF,
        //	SHADOW_CONFIG_1_MAP,
        //	SHADOW_CONFIG_2_MAPS,
        //	SHADOW_CONFIG_3_MAPS,
        //	SHADOW_CONFIG_4_MAPS
        //}

        // textures
        /*[FieldOffset(0)]*/ /*[MarshalAs(UnmanagedType.ByValArray, SizeConst = (int)ETextureLayer.TL_NUM_TEX_LAYERS)]*/
        private CPaddedPtr[] m_apTextures;

        // lighting model
        /*[FieldOffset(64)]*/
        private ELightingModel m_eLightingModel;

        // ambient
        /*[FieldOffset(68)]*/
        private Vec3 m_vAmbientColor;
        /*[FieldOffset(80)]*/
        private ELightingEffect m_eAmbientContrast;
        /*[FieldOffset(84)]*/
        private float m_fAmbientContrastFactor;
        /* [FieldOffset(88)]*/
        private bool m_bAmbientOcclusion;

        // diffuse
        /*[FieldOffset(92)]*/
        private Vec3 m_vDiffuseColor;
        /* [FieldOffset(104)] */
        private float m_fDiffuseScalar;
        /*[FieldOffset(108)]*/
        private bool m_bDiffuseAlphaMaskIsOpaque;

        // detail
        /*[FieldOffset(112)]*/
        private ELightingEffect m_eDetailLayer;

        // specular
        /*[FieldOffset(116)] */
        private ELightingEffect m_eSpecular;
        /*[FieldOffset(120)]*/
        private float m_fShininess;
        /*[FieldOffset(124)]*/
        private Vec3 m_vSpecularColor;

        // transmission
        /*[FieldOffset(136)]*/
        private ELightingEffect m_eTransmission;
        /*[FieldOffset(140)]*/
        private Vec3 m_vTransmissionColor;
        /*[FieldOffset(152)]*/
        private float m_fTransmissionShadowBrightness;
        /*[FieldOffset(156)]*/
        private float m_fTransmissionViewDependency;

        // branch seam smoothing
        /*[FieldOffset(160)]*/
        private ELightingEffect m_eBranchSeamSmoothing;
        /*[FieldOffset(164)]*/
        private float m_fBranchSeamWeight;

        // LOD parameters
        /*[FieldOffset(168)]*/
        private ELodMethod m_eLodMethod;
        /*[FieldOffset(172)]*/
        private bool m_bFadeToBillboard;
        /*[FieldOffset(173)]*/
        private bool m_bVertBillboard;
        /*[FieldOffset(174)]*/
        private bool m_bHorzBillboard;

        // render states
        /*[FieldOffset(176)]*/
        private EShaderGenerationMode m_eShaderGenerationMode;
        /*[FieldOffset(180)]*/
        private bool m_bUsedAsGrass;
        /*[FieldOffset(184)] */
        private ECullType m_eFaceCulling;
        /*[FieldOffset(188)]*/
        private bool m_bBlending;
        /*[FieldOffset(192)]*/
        private ELightingEffect m_eAmbientImageLighting;
        /*[FieldOffset(196)]*/
        private ELightingEffect m_eHueVariation;

        // fog
        /* [FieldOffset(200)]*/
        private EFogCurve m_eFogCurve;                  // determines how fog effect is distributed over distance
        /*[FieldOffset(204)]*/
        private EFogColorType m_eFogColorStyle;             // determines how the fog color is determined (e.g. gradually
                                                            // fog to a single color or to a complex value)
                                                            // shadows
        /*[FieldOffset(208)]*/
        private bool m_bCastsShadows;
        /*[FieldOffset(209)]*/
        private bool m_bReceivesShadows;
        /*[FieldOffset(210)] */
        private bool m_bShadowSmoothing;

        // alpha effects
        /*[FieldOffset(212)]*/
        private float m_fAlphaScalar;

        // wind
        /* [FieldOffset(216)] */
        private EWindLod m_eWindLod;

        // non-lighting shaders
        /* [FieldOffset(220)]*/
        private ERenderPass m_eRenderPass;

        // geometry types
        /*[FieldOffset(224)] */
        private bool m_bBranchesPresent;
        /*[FieldOffset(225)]*/
        private bool m_bFrondsPresent;
        /*[FieldOffset(226)]*/
        private bool m_bLeavesPresent;
        /*[FieldOffset(227)]*/
        private bool m_bFacingLeavesPresent;
        /*[FieldOffset(228)]*/
        private bool m_bRigidMeshesPresent;

        // vertex format data
        /*[FieldOffset(229)]*/
        private SVertexDecl m_sVertexDecl;

        // misc
        /*[FieldOffset(704)]*/
        private CPaddedPtr m_pDescription;
        /*[FieldOffset(712)]*/
        private CPaddedPtr m_pUserData;

        #region Properties

        public CPaddedPtr[] ApTextures { get => m_apTextures; set => m_apTextures = value; }
        public ELightingModel ELightingModel { get => m_eLightingModel; set => m_eLightingModel = value; }
        public Vec3 VAmbientColor { get => m_vAmbientColor; set => m_vAmbientColor = value; }
        public ELightingEffect EAmbientContrast { get => m_eAmbientContrast; set => m_eAmbientContrast = value; }
        public float FAmbientContrastFactor { get => m_fAmbientContrastFactor; set => m_fAmbientContrastFactor = value; }
        public bool BAmbientOcclusion { get => m_bAmbientOcclusion; set => m_bAmbientOcclusion = value; }
        public Vec3 VDiffuseColor { get => m_vDiffuseColor; set => m_vDiffuseColor = value; }
        public float FDiffuseScalar { get => m_fDiffuseScalar; set => m_fDiffuseScalar = value; }
        public bool BDiffuseAlphaMaskIsOpaque { get => m_bDiffuseAlphaMaskIsOpaque; set => m_bDiffuseAlphaMaskIsOpaque = value; }
        public ELightingEffect EDetailLayer { get => m_eDetailLayer; set => m_eDetailLayer = value; }
        public ELightingEffect ESpecular { get => m_eSpecular; set => m_eSpecular = value; }
        public float FShininess { get => m_fShininess; set => m_fShininess = value; }
        public Vec3 VSpecularColor { get => m_vSpecularColor; set => m_vSpecularColor = value; }
        public ELightingEffect ETransmission { get => m_eTransmission; set => m_eTransmission = value; }
        public Vec3 VTransmissionColor { get => m_vTransmissionColor; set => m_vTransmissionColor = value; }
        public float FTransmissionShadowBrightness { get => m_fTransmissionShadowBrightness; set => m_fTransmissionShadowBrightness = value; }
        public float FTransmissionViewDependency { get => m_fTransmissionViewDependency; set => m_fTransmissionViewDependency = value; }
        public ELightingEffect EBranchSeamSmoothing { get => m_eBranchSeamSmoothing; set => m_eBranchSeamSmoothing = value; }
        public float FBranchSeamWeight { get => m_fBranchSeamWeight; set => m_fBranchSeamWeight = value; }
        public ELodMethod ELodMethod { get => m_eLodMethod; set => m_eLodMethod = value; }
        public bool BFadeToBillboard { get => m_bFadeToBillboard; set => m_bFadeToBillboard = value; }
        public bool BVertBillboard { get => m_bVertBillboard; set => m_bVertBillboard = value; }
        public bool BHorzBillboard { get => m_bHorzBillboard; set => m_bHorzBillboard = value; }
        public EShaderGenerationMode EShaderGenerationMode { get => m_eShaderGenerationMode; set => m_eShaderGenerationMode = value; }
        public bool BUsedAsGrass { get => m_bUsedAsGrass; set => m_bUsedAsGrass = value; }
        public ECullType EFaceCulling { get => m_eFaceCulling; set => m_eFaceCulling = value; }
        public bool BBlending { get => m_bBlending; set => m_bBlending = value; }
        public ELightingEffect EAmbientImageLighting { get => m_eAmbientImageLighting; set => m_eAmbientImageLighting = value; }
        public ELightingEffect EHueVariation { get => m_eHueVariation; set => m_eHueVariation = value; }
        public EFogCurve EFogCurve { get => m_eFogCurve; set => m_eFogCurve = value; }
        public EFogColorType EFogColorStyle { get => m_eFogColorStyle; set => m_eFogColorStyle = value; }
        public bool BCastsShadows { get => m_bCastsShadows; set => m_bCastsShadows = value; }
        public bool BReceivesShadows { get => m_bReceivesShadows; set => m_bReceivesShadows = value; }
        public bool BShadowSmoothing { get => m_bShadowSmoothing; set => m_bShadowSmoothing = value; }
        public float FAlphaScalar { get => m_fAlphaScalar; set => m_fAlphaScalar = value; }
        public EWindLod EWindLod { get => m_eWindLod; set => m_eWindLod = value; }
        public ERenderPass ERenderPass { get => m_eRenderPass; set => m_eRenderPass = value; }
        public bool BBranchesPresent { get => m_bBranchesPresent; set => m_bBranchesPresent = value; }
        public bool BFrondsPresent { get => m_bFrondsPresent; set => m_bFrondsPresent = value; }
        public bool BLeavesPresent { get => m_bLeavesPresent; set => m_bLeavesPresent = value; }
        public bool BFacingLeavesPresent { get => m_bFacingLeavesPresent; set => m_bFacingLeavesPresent = value; }
        public bool BRigidMeshesPresent { get => m_bRigidMeshesPresent; set => m_bRigidMeshesPresent = value; }
        public SVertexDecl SVertexDecl { get => m_sVertexDecl; set => m_sVertexDecl = value; }
        public CPaddedPtr PDescription { get => m_pDescription; set => m_pDescription = value; }
        public CPaddedPtr PUserData { get => m_pUserData; set => m_pUserData = value; }

        #endregion Properties

        public void Read(BinaryReader br, List<string> stringtable)
        {
            var startpos = br.BaseStream.Position;

            m_apTextures = new CPaddedPtr[(int)ETextureLayer.TL_NUM_TEX_LAYERS];
            for (int i = 0; i < (int)ETextureLayer.TL_NUM_TEX_LAYERS; i++)
            {
                m_apTextures[i].Read(br, stringtable);
            }
            m_eLightingModel = (ELightingModel)br.ReadInt32();
            m_vAmbientColor = ReadStruct<Vec3>(br);
            m_eAmbientContrast = (ELightingEffect)br.ReadInt32();
            m_fAmbientContrastFactor = br.ReadSingle();
            m_bAmbientOcclusion = br.ReadBoolean();
            br.ParseUntilAligned();

            // diffuse
            m_vDiffuseColor = ReadStruct<Vec3>(br);
            m_fDiffuseScalar = br.ReadSingle();
            m_bDiffuseAlphaMaskIsOpaque = br.ReadBoolean();
            br.ParseUntilAligned();

            // detail
            m_eDetailLayer = (ELightingEffect)br.ReadInt32();

            // specular
            m_eSpecular = (ELightingEffect)br.ReadInt32();
            m_fShininess = br.ReadSingle();
            m_vSpecularColor = ReadStruct<Vec3>(br);

            // transmission
            m_eTransmission = (ELightingEffect)br.ReadInt32();
            m_vTransmissionColor = ReadStruct<Vec3>(br);
            m_fTransmissionShadowBrightness = br.ReadSingle();
            m_fTransmissionViewDependency = br.ReadSingle();

            // branch seam smoothing
            m_eBranchSeamSmoothing = (ELightingEffect)br.ReadInt32();
            m_fBranchSeamWeight = br.ReadSingle();

            // LOD parameters
            m_eLodMethod = (ELodMethod)br.ReadInt32();
            m_bFadeToBillboard = br.ReadBoolean();
            m_bVertBillboard = br.ReadBoolean();
            m_bHorzBillboard = br.ReadBoolean();
            br.ParseUntilAligned();

            // render states
            m_eShaderGenerationMode = (EShaderGenerationMode)br.ReadInt32();
            m_bUsedAsGrass = br.ReadBoolean();
            br.ParseUntilAligned();
            m_eFaceCulling = (ECullType)br.ReadInt32();
            m_bBlending = br.ReadBoolean();
            br.ParseUntilAligned();
            m_eAmbientImageLighting = (ELightingEffect)br.ReadInt32();
            m_eHueVariation = (ELightingEffect)br.ReadInt32();

            // fog
            m_eFogCurve = (EFogCurve)br.ReadInt32();
            m_eFogColorStyle = (EFogColorType)br.ReadInt32();
            m_bCastsShadows = br.ReadBoolean();
            m_bReceivesShadows = br.ReadBoolean();
            m_bShadowSmoothing = br.ReadBoolean();
            br.ParseUntilAligned();

            // alpha effects
            m_fAlphaScalar = br.ReadSingle();

            // wind
            m_eWindLod = (EWindLod)br.ReadInt32();

            // non-lighting shaders
            m_eRenderPass = (ERenderPass)br.ReadInt32();

            // geometry types
            m_bBranchesPresent = br.ReadBoolean();
            m_bFrondsPresent = br.ReadBoolean();
            m_bLeavesPresent = br.ReadBoolean();
            m_bFacingLeavesPresent = br.ReadBoolean();
            m_bRigidMeshesPresent = br.ReadBoolean();

            // vertex format data
            m_sVertexDecl = ReadStruct<SVertexDecl>(br);
            br.ParseUntilAligned();

            // misc
            m_pDescription.Read(br, stringtable);
            m_pUserData.Read(br, stringtable);

            var endpos = br.BaseStream.Position;
            var readbytes = endpos - startpos;
            if (readbytes != 720)
                throw new NotImplementedException();
        }

        public override string ToString() => m_pDescription.ToString();

        internal void Write(BinaryWriter file, List<string> stringtable)
        {
            for (int i = 0; i < m_apTextures.Length; i++)
            {
                m_apTextures[i].Write(file, stringtable);
            }
            file.Write((int)m_eLightingModel);
            WriteStruct<Vec3>(m_vAmbientColor, file.BaseStream);
            file.Write((int)m_eAmbientContrast);
            file.Write(m_fAmbientContrastFactor);
            file.Write(m_bAmbientOcclusion);
            file.WriteUntilAligned();

            // diffuse
            WriteStruct<Vec3>(m_vDiffuseColor, file.BaseStream);
            file.Write(m_fDiffuseScalar);
            file.Write(m_bDiffuseAlphaMaskIsOpaque);
            file.WriteUntilAligned();

            // detail
            file.Write((int)m_eDetailLayer);

            // specular
            file.Write((int)m_eSpecular);
            file.Write(m_fShininess);
            WriteStruct<Vec3>(m_vSpecularColor, file.BaseStream);

            // transmission
            file.Write((int)m_eTransmission);
            WriteStruct<Vec3>(m_vTransmissionColor, file.BaseStream);
            file.Write(m_fTransmissionShadowBrightness);
            file.Write(m_fTransmissionViewDependency);

            // branch seam smoothing
            file.Write((int)m_eBranchSeamSmoothing);
            file.Write(m_fBranchSeamWeight);

            // LOD parameters
            file.Write((int)m_eLodMethod);
            file.Write(m_bFadeToBillboard);
            file.Write(m_bVertBillboard);
            file.Write(m_bHorzBillboard);
            file.WriteUntilAligned();

            // render states
            file.Write((int)m_eShaderGenerationMode);
            file.Write(m_bUsedAsGrass);
            file.WriteUntilAligned();
            file.Write((int)m_eFaceCulling);
            file.Write(m_bBlending);
            file.WriteUntilAligned();
            file.Write((int)m_eAmbientImageLighting);
            file.Write((int)m_eHueVariation);

            // fog
            file.Write((int)m_eFogCurve);
            file.Write((int)m_eFogColorStyle);
            file.Write(m_bCastsShadows);
            file.Write(m_bReceivesShadows);
            file.Write(m_bShadowSmoothing);
            file.WriteUntilAligned();

            // alpha effects
            file.Write(m_fAlphaScalar);

            // wind
            file.Write((int)m_eWindLod);

            // non-lighting shaders
            file.Write((int)m_eRenderPass);

            // geometry types
            file.Write(m_bBranchesPresent);
            file.Write(m_bFrondsPresent);
            file.Write(m_bLeavesPresent);
            file.Write(m_bFacingLeavesPresent);
            file.Write(m_bRigidMeshesPresent);

            // vertex format data
            WriteStruct<SVertexDecl>(m_sVertexDecl, file.BaseStream);
            file.WriteUntilAligned();

            // misc
            m_pDescription.Write(file, stringtable);
            m_pUserData.Write(file, stringtable);
        }

        private T ReadStruct<T>(BinaryReader br) where T : struct
        {
            var size = Marshal.SizeOf<T>();

            var m_temp = new byte[size];
            br.BaseStream.Read(m_temp, 0, size);

            var handle = GCHandle.Alloc(m_temp, GCHandleType.Pinned);
            var item = Marshal.PtrToStructure<T>(handle.AddrOfPinnedObject());

            handle.Free();

            return item;
        }

        private void WriteStruct<T>(T value, Stream stream) where T : struct
        {
            var m_temp = new byte[Marshal.SizeOf<T>()];
            var handle = GCHandle.Alloc(m_temp, GCHandleType.Pinned);

            Marshal.StructureToPtr(value, handle.AddrOfPinnedObject(), true);
            stream.Write(m_temp, 0, m_temp.Length);

            handle.Free();
        }
    }

    ///////////////////////////////////////////////////////////////////////
    //  Structure SVertexDecl
    //
    //	Depending on the compilation mode chosen in the Compiler app, vertex
    //	declarations may be variable from draw call to draw call. This structure
    //	houses a portable definition of the vertex declaration.

    [StructLayout(LayoutKind.Sequential, Pack = 1)] //Size = 472
    [TypeConverter(typeof(ExpandableObjectConverter))]
    public struct SVertexDecl
    {
        [StructLayout(LayoutKind.Sequential, Pack = 1)] //Size = 14
        [TypeConverter(typeof(ExpandableObjectConverter))]
        public struct SAttribute
        {
            private sbyte m_uiStream;
            private EVertexFormat m_eFormat;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = (int)EVertexComponent.VERTEX_COMPONENT_COUNT)] private EVertexProperty[] m_aeProperties/*[VERTEX_COMPONENT_COUNT]*/;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = (int)EVertexComponent.VERTEX_COMPONENT_COUNT)] private EVertexComponent[] m_aePropertyComponents/*[VERTEX_COMPONENT_COUNT]*/;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = (int)EVertexComponent.VERTEX_COMPONENT_COUNT)] private sbyte[] m_auiVertexOffsets/*[VERTEX_COMPONENT_COUNT]*/;

            #region Properties

            public EVertexProperty[] AeProperties { get => m_aeProperties; set => m_aeProperties = value; }
            public EVertexComponent[] AePropertyComponents { get => m_aePropertyComponents; set => m_aePropertyComponents = value; }
            public sbyte[] AuiVertexOffsets { get => m_auiVertexOffsets; set => m_auiVertexOffsets = value; }
            public sbyte UiStream { get => m_uiStream; set => m_uiStream = value; }
            public EVertexFormat EFormat { get => m_eFormat; set => m_eFormat = value; }

            #endregion Properties
        }

        [StructLayout(LayoutKind.Sequential, Pack = 1)] //Size = 13
        [TypeConverter(typeof(ExpandableObjectConverter))]
        public struct SProperty
        {
            private EVertexFormat m_eFormat;                                   // all four components in attribute have to be the same format
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = (int)EVertexComponent.VERTEX_COMPONENT_COUNT)] private EVertexAttribute[] m_aeAttribs/*[VERTEX_COMPONENT_COUNT]*/;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = (int)EVertexComponent.VERTEX_COMPONENT_COUNT)] private EVertexComponent[] m_aeAttribComponents/*[VERTEX_COMPONENT_COUNT]*/;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = (int)EVertexComponent.VERTEX_COMPONENT_COUNT)] private sbyte[] m_auiOffsets/*[VERTEX_COMPONENT_COUNT]*/;           // in bytes, offset of each component from beginning of whole vertex

            #region Properties

            public EVertexAttribute[] AeAttribs { get => m_aeAttribs; set => m_aeAttribs = value; }
            public EVertexComponent[] AeAttribComponents { get => m_aeAttribComponents; set => m_aeAttribComponents = value; }
            public sbyte[] AuiOffsets { get => m_auiOffsets; set => m_auiOffsets = value; }
            public EVertexFormat EFormat { get => m_eFormat; set => m_eFormat = value; }

            #endregion Properties
        }

        // mesh instancing support; mostly for PS Vita platform
        //enum EInstanceType
        //{
        //    INSTANCES_3D_TREES,
        //    INSTANCES_GRASS,
        //    INSTANCES_BILLBOARDS,
        //    INSTANCES_NONE
        //}

        // utility for setting manually
        //[StructLayout(LayoutKind.Sequential, Pack = 4)]
        //public struct SAttribDesc
        //{
        //	sbyte m_uiStream;
        //          EVertexAttribute m_eAttrib;
        //          EVertexFormat m_eFormat;
        //          sbyte m_uiNumComponents; // e.g., 3 for (x,y,z)

        //          public struct SPropertyComponent
        //	{
        //		EVertexProperty m_eProperty;
        //              EVertexComponent m_eComponent;
        //	}
        //	SPropertyComponent[] m_asProperties/*[VERTEX_COMPONENT_COUNT]*/;
        //}

        // vertex data organized by attributes
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = (int)EVertexAttribute.VERTEX_ATTRIB_COUNT)] private SAttribute[] m_asAttributes/*[VERTEX_ATTRIB_COUNT]*/;

        // same vertex data, but organized by properties
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = (int)EVertexProperty.VERTEX_PROPERTY_COUNT)] private SProperty[] m_asProperties/*[VERTEX_PROPERTY_COUNT]*/;

        // shared by both organizations
        private sbyte m_uiVertexSize;

        #region Properties

        public SAttribute[] AsAttributes { get => m_asAttributes; set => m_asAttributes = value; }
        public SProperty[] AsProperties { get => m_asProperties; set => m_asProperties = value; }
        public sbyte UiVertexSize { get => m_uiVertexSize; set => m_uiVertexSize = value; }

        #endregion Properties
    }

    ///////////////////////////////////////////////////////////////////////
    //  A word on terminology regarding vertex declarations in this SDK. There
    //	are several key terms that need to be clearly defined. Take a vertex
    //	shader input declaration like the following in HLSL syntax:
    //
    //	struct SVertexDecl
    //	{
    //	  float3  vSlot0  : POSITION;	// xyz = position.xyz
    //    float4  vSlot1  : TEXCOORD0;	// xy = diffuse texcoords, z = amb occ, w = normal.z
    //  };
    //
    //	Using the second line, vSlot1, of the structure as an example, we wish to
    //  define the following terms (appear in double-quotes):
    //
    //		o The entire float4 group taken collectively is termed a "vertex attribute."
    //      o TEXCOORD0 is the vertex's "semantic," as the term defined is in the HLSL
    //		  and Cg docs.
    //		o "Component" refers any single x, y, z, or w in the slot.
    //      o The four components hold diffuse texcoords, ambient occlusion, and partial
    //		  normal "properties."  Not to be confused with "attribute," as there may be multiple
    //        properties in a single attribute, or a single property may be split across
    //        multiple attributes.
    //		o "Format" refers to how the data was stored in the vertex buffer (e.g.
    //		   byte, half float, full float) { not visible in this decl }
    //
    //	The structure taken as a whole is the "vertex declaration."

    ///////////////////////////////////////////////////////////////////////
    //  Enumeration EVertexProperty
    //
    //	Contains a list of all vertex properties that may be used by the SpeedTree
    //	vertex buffers and shaders. Note that some are geometry-specific and,
    //	depending on the compilation mode chosen in the Compiler app, may or
    //	may not appear in the vertex decl for each draw call.
    //
    //	Details for each vertex property can be queried using
    //	CCore::GetVertexPropertyDesc().
    //
    //	The typedef below forces the enumeration to use 8 bits instead of
    //	32. It helps to keep SVertexDecl small.

    public enum EVertexProperty/*Untyped*/ : sbyte
    {
        VERTEX_PROPERTY_UNASSIGNED = -1,

        // these can affect the shape of the tree and therefore come first; this
        // can reduce vertex fetches when rendering depth & shadow passes
        VERTEX_PROPERTY_POSITION,                       // 3 components

        VERTEX_PROPERTY_DIFFUSE_TEXCOORDS,              // 2 components
        VERTEX_PROPERTY_NORMAL,                         // 3 components (normal impacts the wind algorithm)
        VERTEX_PROPERTY_LOD_POSITION,                   // 3 components
        VERTEX_PROPERTY_GEOMETRY_TYPE_HINT,             // 1 component
        VERTEX_PROPERTY_LEAF_CARD_CORNER,               // 3 components (corner x, corner y, z-fight offset)
        VERTEX_PROPERTY_LEAF_CARD_LOD_SCALAR,           // 1 component
        VERTEX_PROPERTY_LEAF_CARD_SELF_SHADOW_OFFSET,   // 1 component
        VERTEX_PROPERTY_WIND_BRANCH_DATA,               // 4 components
        VERTEX_PROPERTY_WIND_EXTRA_DATA,                // 3 components
        VERTEX_PROPERTY_WIND_FLAGS,                     // 1 component
        VERTEX_PROPERTY_LEAF_ANCHOR_POINT,              // 3 components
        VERTEX_PROPERTY_BONE_ID,                        // 1 component

        // these do not affect the shape of the tree and come later
        VERTEX_PROPERTY_BRANCH_SEAM_DIFFUSE,            // 3 components (s, t, weight)

        VERTEX_PROPERTY_BRANCH_SEAM_DETAIL,             // 2 components (s, t)
        VERTEX_PROPERTY_DETAIL_TEXCOORDS,               // 2 components
        VERTEX_PROPERTY_TANGENT,                        // 3 components
        VERTEX_PROPERTY_LIGHTMAP_TEXCOORDS,             // 2 components
        VERTEX_PROPERTY_AMBIENT_OCCLUSION,              // 1 component
        VERTEX_PROPERTY_MISC_SEMANTIC = VERTEX_PROPERTY_AMBIENT_OCCLUSION,

        VERTEX_PROPERTY_COUNT,
        VERTEX_PROPERTY_PAD = VERTEX_PROPERTY_COUNT
    };

    //typedef Enumeration<EVertexPropertyUntyped, st_int8> EVertexProperty;

    ///////////////////////////////////////////////////////////////////////
    //  Structure SVertexPropertyDesc
    //
    //	Holds details about each vertex property as returned by
    //	CCore::GetVertexPropertyDesc().

#pragma warning disable CS0169 // ~~~[[maybe_unused]] c++ compiler attribute
#pragma warning disable IDE0051

    public struct SVertexPropertyDesc
    {
        private int m_nNumComponents;
        private string m_pFullName;
        private string m_pShortName;
    };

#pragma warning restore CS0169 // ~~~[[maybe_unused]] c++ compiler attribute
#pragma warning restore IDE0051

    ///////////////////////////////////////////////////////////////////////
    //  Enumeration EPixelProperty
    //
    //	Contains a list of all pixel shader properties that may be used by the
    //	SpeedTree shaders. While this is housed in the Core SDK library, it
    //	is primarily used by the SRT Exporter housed in the Compiler app.
    //
    //	Details for each vertex property can be queried using
    //	CCore::GetPixelPropertyDesc().
    //
    //	The typedef below forces the enumeration to use 8 bits instead of
    //	32. It helps to keep SVertexDecl small.

    public enum EPixelProperty : sbyte
    {
        PIXEL_PROPERTY_POSITION,
        PIXEL_PROPERTY_FOG_SCALAR,
        PIXEL_PROPERTY_FOG_COLOR,
        PIXEL_PROPERTY_DIFFUSE_TEXCOORDS,
        PIXEL_PROPERTY_DETAIL_TEXCOORDS,
        PIXEL_PROPERTY_PER_VERTEX_LIGHTING_COLOR,
        PIXEL_PROPERTY_NORMAL_MAP_VECTOR,
        PIXEL_PROPERTY_NORMAL,
        PIXEL_PROPERTY_BINORMAL,
        PIXEL_PROPERTY_TANGENT,
        PIXEL_PROPERTY_SPECULAR_HALF_VECTOR,
        PIXEL_PROPERTY_PER_VERTEX_SPECULAR_DOT,
        PIXEL_PROPERTY_PER_VERTEX_AMBIENT_CONTRAST,
        PIXEL_PROPERTY_FADE_TO_BILLBOARD,
        PIXEL_PROPERTY_TRANSMISSION_FACTOR,
        PIXEL_PROPERTY_RENDER_EFFECT_FADE,
        PIXEL_PROPERTY_AMBIENT_OCCLUSION,
        PIXEL_PROPERTY_BRANCH_SEAM_DIFFUSE,
        PIXEL_PROPERTY_BRANCH_SEAM_DETAIL,
        PIXEL_PROPERTY_SHADOW_DEPTH,
        PIXEL_PROPERTY_SHADOW_MAP_0_PROJECTION,
        PIXEL_PROPERTY_SHADOW_MAP_1_PROJECTION,
        PIXEL_PROPERTY_SHADOW_MAP_2_PROJECTION,
        PIXEL_PROPERTY_SHADOW_MAP_3_PROJECTION,
        PIXEL_PROPERTY_HUE_VARIATION,
        PIXEL_PROPERTY_COUNT
    };

    ///////////////////////////////////////////////////////////////////////
    //  Structure SPixelPropertyDesc
    //
    //	Holds details about each pixel property as returned by
    //	CCore::GetPixelPropertyDesc().

#pragma warning disable CS0169 // ~~~[[maybe_unused]] c++ compiler attribute
#pragma warning disable IDE0051

    internal struct SPixelPropertyDesc
    {
        private int m_nNumComponents;
        private string m_pFullName;
        private string m_pShortName;
    };

#pragma warning restore CS0169 // ~~~[[maybe_unused]] c++ compiler attribute
#pragma warning restore IDE0051

    ///////////////////////////////////////////////////////////////////////
    //  Enumeration EVertexAttribute
    //
    //	List the 16 available vertex attributes. Each can hold 4 floats. Each
    //	platform supported in the SDK will turn these into platform-specific
    //	vertex shader input semantics like POSITION or TEXCOORD[0-15].
    //
    //	The typedef below forces the enumeration to use 8 bits instead of
    //	32. It helps to keep SVertexDecl small.

    public enum EVertexAttribute/*Untyped*/ : sbyte
    {
        VERTEX_ATTRIB_UNASSIGNED = -1,

        VERTEX_ATTRIB_0,
        VERTEX_ATTRIB_1,
        VERTEX_ATTRIB_2,
        VERTEX_ATTRIB_3,
        VERTEX_ATTRIB_4,
        VERTEX_ATTRIB_5,
        VERTEX_ATTRIB_6,
        VERTEX_ATTRIB_7,
        VERTEX_ATTRIB_8,
        VERTEX_ATTRIB_9,
        VERTEX_ATTRIB_10,
        VERTEX_ATTRIB_11,
        VERTEX_ATTRIB_12,
        VERTEX_ATTRIB_13,
        VERTEX_ATTRIB_14,
        VERTEX_ATTRIB_15,

        VERTEX_ATTRIB_COUNT,
        VERTEX_DECL_END = VERTEX_ATTRIB_COUNT
    };

    //typedef Enumeration<EVertexAttributeUntyped, st_int8> EVertexAttribute;

    ///////////////////////////////////////////////////////////////////////
    //  Enumeration EVertexComponent
    //
    //	Simple enumeration for accessing individual components for attributes.
    //
    //	The typedef below forces the enumeration to use 8 bits instead of
    //	32. It helps to keep SVertexDecl small.

    public enum EVertexComponent/*Untyped*/ : sbyte
    {
        VERTEX_COMPONENT_UNASSIGNED = -1,

        VERTEX_COMPONENT_X,
        VERTEX_COMPONENT_Y,
        VERTEX_COMPONENT_Z,
        VERTEX_COMPONENT_W,
        VERTEX_COMPONENT_COUNT
    };

    //typedef Enumeration<EVertexComponentUntyped, st_int8> EVertexComponent;

    ///////////////////////////////////////////////////////////////////////
    //  Enumeration EVertexFormat
    //
    //	List of the three data types used to house vertex data.
    //
    //	The typedef below forces the enumeration to use 8 bits instead of
    //	32. It helps to keep SVertexDecl::SAttribDesc small.

    public enum EVertexFormat/*Untyped*/ : sbyte
    {
        VERTEX_FORMAT_UNASSIGNED = -1,

        VERTEX_FORMAT_FULL_FLOAT,       // 32-bit floats
        VERTEX_FORMAT_HALF_FLOAT,       // 16-bit floats
        VERTEX_FORMAT_BYTE,             // 8-bit values
        VERTEX_FORMAT_COUNT
    };

    //typedef Enumeration<EVertexFormatUntyped, st_int8> EVertexFormat;

    ///////////////////////////////////////////////////////////////////////
    //  Enumerations associated with SRenderState

    public enum ELightingModel
    {
        LIGHTING_MODEL_PER_VERTEX,
        LIGHTING_MODEL_PER_PIXEL,
        LIGHTING_MODEL_PER_VERTEX_X_PER_PIXEL, // transitional state, forward rendering only
        LIGHTING_MODEL_DEFERRED
    }

    public enum ELightingEffect
    {
        EFFECT_OFF,
        EFFECT_ON,
        EFFECT_OFF_X_ON // transitional state
    }

    public enum ELodMethod
    {
        LOD_METHOD_POP,
        LOD_METHOD_SMOOTH
    }

    public enum ECullType
    {
        CULLTYPE_NONE,
        CULLTYPE_BACK,
        CULLTYPE_FRONT
    }

    public enum EFogCurve
    {
        FOG_CURVE_NONE,             // no fog effect
        FOG_CURVE_LINEAR,
        FOG_CURVE_EXP,
        FOG_CURVE_EXP2,
        FOG_CURVE_USER
    }

    public enum EFogColorType
    {
        FOG_COLOR_TYPE_CONSTANT,
        FOG_COLOR_TYPE_DYNAMIC
    }

    public enum EWindLod
    {
        WIND_LOD_NONE,
        WIND_LOD_GLOBAL,
        WIND_LOD_BRANCH,
        WIND_LOD_FULL,

        // transitional states (affect shaders only), 'X' in name denotes cross fade
        WIND_LOD_NONE_X_GLOBAL,

        WIND_LOD_NONE_X_BRANCH,
        WIND_LOD_NONE_X_FULL,
        WIND_LOD_GLOBAL_X_BRANCH,
        WIND_LOD_GLOBAL_X_FULL,
        WIND_LOD_BRANCH_X_FULL
    }

    // used by the Compiler app when writing shaders
    public enum EWindEffect
    {
        WIND_EFFECT_LEAF_WIND_1,
        WIND_EFFECT_LEAF_WIND_2
    }

    // these generation modes match one-for-one with the Compiler app's "Merge materials" compilation option
    public enum EShaderGenerationMode
    {
        SHADER_GEN_MODE_STANDARD,
        SHADER_GEN_MODE_ACROSS_GEOMETRIES,
        SHADER_GEN_MODE_AGGRESSIVE_ROUND_UP,
        SHADER_GEN_MODE_AGGRESSIVE_ROUND_DOWN,
        SHADER_GEN_MODE_SPEEDTREE_5X_STYLE,
        SHADER_GEN_MODE_UNIFIED_SHADERS,
        SHADER_GEN_MODE_UNREAL_ENGINE_4,
        SHADER_GEN_MODE_COUNT
    }
}
